package main

import (
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"time"
)

// 实现分布式乐观锁
func main() {
	cli := getEtcdCli()
	if cli == nil {
		fmt.Println("getEtcdCli failed.")
		return
	}
	defer cli.Close()

	// lease 实现锁自动过期
	// op操作
	// txn事务: if else then

	// 1. 上锁(创建租约、自动续租、拿着租约抢占锁)
	lease := clientv3.NewLease(cli)

	// 5秒租约
	grant, err := lease.Grant(context.TODO(), 5)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 准备一个用于取消自动续租的 ctx
	ctx, cancelFunc := context.WithCancel(context.TODO())

	// 函数退出后自动续租停止
	defer cancelFunc()
	// 撤销租约
	defer lease.Revoke(context.TODO(), grant.ID)

	// 自动续租
	keepRespChan, err := lease.KeepAlive(ctx, grant.ID)
	if err != nil {
		fmt.Println(err)
		return
	}

	// 处理续约应答的协程
	go handleGrandResp(keepRespChan)

	//  if 不存在key， then 设置它, else 抢锁失败
	// 创建事务
	txn := cli.Txn(context.TODO())

	txn.If(clientv3.Compare(clientv3.CreateRevision("/cron/lock/job9"), "=", 0)).
		Then(clientv3.OpPut("/cron/lock/job9", "client1", clientv3.WithLease(grant.ID))).
		Else(clientv3.OpGet("/cron/lock/job9")) // 否则抢锁失败

	// 提交事务
	txnResp, err := txn.Commit()
	if err != nil {
		fmt.Println(err)
		return
	}

	// 判断是否抢到了锁
	if !txnResp.Succeeded {
		fmt.Println("锁被占用:", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	}

	// 2. 处理业务
	fmt.Println("安全的处理业务")
	time.Sleep(10 * time.Second)

	// 3. 释放锁(取消自动续租、释放租约)
	// defer 会把租约释放掉, 关联的KV就被删除了
}

func getEtcdCli() *clientv3.Client {
	cli, err := clientv3.New(clientv3.Config{
		Endpoints:   []string{"10.0.0.11:2379"},
		DialTimeout: 5 * time.Second,
	})
	if err != nil {
		fmt.Println(err)
		return nil
	}
	return cli
}

func handleGrandResp(keepRespChan <-chan *clientv3.LeaseKeepAliveResponse) {
	for keepResp := range keepRespChan {
		fmt.Println("收到自动续租应答:", keepResp.ID)
	}
	fmt.Println("租约已经失效了")
}
